package com.dd.keel.core.common.utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.StringWriter;
import java.net.URL;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Attribute;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.dom4j.DocumentHelper;

import org.apache.xerces.parsers.SAXParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.helpers.XMLReaderFactory;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;

import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;

/**
 * XML util
 * @author Kevin.XU
 *
 */
public class XMLUtil {
	
	final static private Logger LOGGER = LoggerFactory.getLogger(XMLUtil.class);
	
	public static Document createDocument()
	{
		try{
			Document document = DocumentHelper.createDocument();
			document.setXMLEncoding("UTF-8");
			return document;
		}catch(Exception ex){
			LOGGER.error(ex.getMessage(), ex);
		}
		return null;
	}
	
	public static Document createDocument(String charsetName)
	{
		try{
			Document document = DocumentHelper.createDocument();
			document.setXMLEncoding(charsetName);
			return document;
		}catch(Exception ex){
			LOGGER.error(ex.getMessage(), ex);
		}
		return null;
	}
	
	public static String readTextFromDocument(Document document) throws Exception
	{
		return document.asXML();
	}
	
	public static String readFormatTextFromDocument(Document document) throws Exception
	{
		StringWriter stringWriter = new StringWriter();
		XMLWriter writer = null ;
		OutputFormat format = OutputFormat.createPrettyPrint();
		writer = new XMLWriter( stringWriter, format );
        writer.write(document);
        writer.close();
		return stringWriter.toString();
	}
	
	public static void writeDocumentToFile(Document document,String outputFilePath) throws Exception
	{
		FileWriter fileWriter = new FileWriter(outputFilePath);
		XMLWriter writer = null ;
		OutputFormat format = OutputFormat.createPrettyPrint();
		writer = new XMLWriter( fileWriter, format );
        writer.write(document);
        writer.close();
	}
	
	public static Document writeTextToDocument(String text) throws Exception 
	{
		Document document = DocumentHelper.parseText(text);
		return document;
	}
	
	public static Element getRootElement(String xmlFileName,Class contextClass,String encoding) throws Exception
	{
		Document document = null;
		Element root = null;
		try{
			//if SAXReader reader = new SAXReader();
			//use system property 'org.xml.sax.driver'
			//-Dorg.xml.sax.driver=org.apache.xerces.parsers.SAXParser			
	        SAXReader reader = new SAXReader("org.apache.xerces.parsers.SAXParser");
	        File file = new File(xmlFileName);
	        InputSource source = null;
	        if(file.exists())
	        {
	        	source = new InputSource(new FileInputStream(file));
	        }
	        else
	        {
	        	source = new InputSource(URLUtil.getURLFromClasspath(xmlFileName,contextClass).openStream());
	        }
            source.setEncoding(encoding);
	        document = reader.read( source );
	        root = document.getRootElement();
	        return root;
		}catch(Exception ex){
			LOGGER.error(ex.getMessage(), ex);
			throw new Exception("Parse "+xmlFileName+" failed : "+ex.getMessage());
		}
	}
	
	public static Element getRootElement(File xmlFile,String encoding) throws Exception
	{
		if( xmlFile==null || !xmlFile.exists() )
		{
			throw new Exception("Xml File ["+ ((xmlFile==null)?"":xmlFile.getAbsolutePath()) +"] is wrong");
		}
		Document document = null;
		Element root = null;
		try{
	        SAXReader reader = new SAXReader("org.apache.xerces.parsers.SAXParser");
	        InputSource source = new InputSource(new FileInputStream(xmlFile));
            source.setEncoding(encoding);
	        document = reader.read( source );
	        root = document.getRootElement();
	        return root;
		}catch(Exception ex){
			LOGGER.error(ex.getMessage(), ex);
			throw new Exception("Parse "+xmlFile.getAbsolutePath()+" failed : "+ex.getMessage());
		}
	}
	
	public static Element getRootElement(String xmlFileName,String encoding) throws Exception
	{
		return getRootElement(xmlFileName,null,encoding);
	}
	
	public static Element getRootElement(File xmlFile) throws Exception
	{
		return getRootElement(xmlFile,"UTF-8");
	}
	
	public static Element getRootElement(String xmlFileName) throws Exception
	{
		return getRootElement(xmlFileName,null,"UTF-8");
	}
	
	public static Element getRootElement(String xmlFileName,Class contextClass) throws Exception
	{
		return getRootElement(xmlFileName,contextClass,"UTF-8");
	}
	
	public static String getAttribute(Element element,String name)
	{
		/*String value = null;
        for ( Iterator i = element.attributeIterator(); i.hasNext(); ) {
            Attribute attribute = (Attribute) i.next();
            if(attribute.getName().equals(name)){
            	value = attribute.getValue();
            	break;
            }
        }
		return value;*/
		return element.attributeValue(name);
	}
	
	public static Map<String,String> getAttributes(Element element)
	{
		HashMap<String,String> attrs = new HashMap<String,String>();
        for ( Iterator i = element.attributeIterator(); i.hasNext(); ) {
            Attribute attribute = (Attribute) i.next();
            String name = attribute.getName();
            String value = attribute.getValue();
            attrs.put(name, value);
        }
		return attrs;
	}
	
	public static Element getExclusiveChildElement(Element element,String childElementName)
	{
		return element.element(childElementName);
	}
	
	/**
	 * 将转义符号转换回来
	 * @param text
	 * @return
	 */
	public static String antiEscape(String text)
	{
		text = text.replaceAll("&lt;", "<");
		text = text.replaceAll("&gt;", ">");
		text = text.replaceAll("&amp;", "&");
		text = text.replaceAll("&apos;", "'");
		text = text.replaceAll("&quot;", "\"");
		return text;
	}
	
	/**
	 * 对内容进行转义
	 * @param text
	 * @return
	 */
	public static String escape(String text)
	{
		text = text.replaceAll("<", "&lt;");
		text = text.replaceAll(">", "&gt;");
		text = text.replaceAll("&", "&amp;");
		text = text.replaceAll("'", "&apos;");
		text = text.replaceAll("\"", "&quot;");
		text = text.replaceAll("#", "&#35;");
		return text;
	}
	
	/**
	 * 返回内容使用CDATA包含
	 * @param text
	 * @return
	 */
	public static String cdata(String text)
	{
		return "<![CDATA["+text+"]]>";
	}
	
	/**
	 * 取子元素的值
	 * @param element
	 * @param childElementName
	 * @return
	 */
	public static String getChildElementText(Element element,String childElementName)
	{
		/*for ( Iterator elementIter = element.elementIterator( childElementName ); elementIter.hasNext(); ) {
			Element childElement = (Element) elementIter.next();
			return childElement.getTextTrim();
		}*/
		Element childElement = getExclusiveChildElement(element,childElementName);
		if(childElement!=null)
		{
			return childElement.getTextTrim();
		}
		else
		{
			return null;
		}
	}
	
	/**
	 * 取子子节点元素的值
	 * @param element
	 * @param childElementName
	 * @param subChildElementName
	 * @return
	 */
	public static String getSubChildElementText(Element element,String childElementName,String subChildElementName)
	{
		/*for ( Iterator elementIter = element.elementIterator( childElementName ); elementIter.hasNext(); ) {
			Element childElement = (Element) elementIter.next();
			return childElement.getTextTrim();
		}*/
		Element childElement = getExclusiveChildElement(element,childElementName);
		if(childElement!=null)
		{
			Element subChildElement = getExclusiveChildElement(childElement,subChildElementName);
			if(subChildElement!=null)
			{
				return subChildElement.getTextTrim();
			}
			else
			{
				return null;
			}
		}
		else
		{
			return null;
		}
	}
	
	public static void saveChildElementText(Element element,String childElementName,String value)
	{
		if(value!=null)
		{
			if(getChildElementText(element, childElementName)==null)
			{
				element.addElement(childElementName).addText(value);
			}
			else
			{
				element.element(childElementName).setText(value);
			}
		}
	}
	
	 /**
     * This method ensures that the output String has only
     * valid XML unicode characters as specified by the
     * XML 1.0 standard. For reference, please see
     * <a href="http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char">the
     * standard</a>. This method will return an empty
     * String if the input is null or empty.
     *
     * @param in The String whose non-valid characters we want to remove.
     * @return The in String, stripped of non-valid characters.
     */
    public static String stripNonValidXMLCharacters(String in) {
    	StringBuilder out = new StringBuilder(); // Used to hold the output.
        char current; // Used to reference the current character.
        if (in == null || ("".equals(in))) return ""; // vacancy test.
        for (int i = 0; i < in.length(); i++) {
            current = in.charAt(i); // NOTE: No IndexOutOfBoundsException caught here; it should not happen.
            if ((current == 0x9) ||
                (current == 0xA) ||
                (current == 0xD) ||
                ((current >= 0x20) && (current <= 0xD7FF)) ||
                ((current >= 0xE000) && (current <= 0xFFFD)) ||
                ((current >= 0x10000) && (current <= 0x10FFFF)))
                out.append(current);
        }
        return out.toString();
    } 
	
    /**
	 *I think that the solution is broken because java chars are less than FFFF
	 * so some comparisons are useless. The idea is to compare Unicode code
	 * points not UTF16 code units(java char type). Here is my solution: This
	 * method ensures that the output String has only valid XML unicode
	 * characters as specified by the XML 1.0 standard. For reference, please
	 * see the standard. This method will return an empty String if the input is
	 * null or empty.
	 * 
	 * @author Donoiu Cristian, GPL
	 * @param The
	 *            String whose non-valid characters we want to remove.
	 * @return The in String, stripped of non-valid characters.
	 */
	public static String removeInvalidXMLCharacters(String s) {
		StringBuilder out = new StringBuilder(); // Used to hold the output.
		int codePoint; // Used to reference the current character.
		// String ss = "\ud801\udc00"; // This is actualy one unicode character,
		// represented by two code units!!!.
		// System.out.println(ss.codePointCount(0, ss.length()));// See: 1
		int i = 0;
		while (i < s.length()) {
			//System.out.println("i=" + i);
			codePoint = s.codePointAt(i); // This is the unicode code of the character.
			if ((codePoint == 0x9)
					|| // Consider testing larger ranges first to improve speed.
					(codePoint == 0xA) || (codePoint == 0xD)
					|| ((codePoint >= 0x20) && (codePoint <= 0xD7FF))
					|| ((codePoint >= 0xE000) && (codePoint <= 0xFFFD))
					|| ((codePoint >= 0x10000) && (codePoint <= 0x10FFFF))) {
				out.append(Character.toChars(codePoint));
			}
			i += Character.charCount(codePoint); 
			// Increment with the number of code units(java chars) needed to represent a Unicode char.
		}
		return out.toString();
	} 
    
}
